#ifndef CORE__TESTS__HH__
#define CORE__TESTS__HH__

#include <LitPlatform.hpp>
#include <LitTimer.h>
#include <LitString.h>

#include <string>
#include <memory>
#include <stack>
#include <unordered_map>
#include <iostream>

namespace Lit
{ 
	namespace Tests
	{ 
		struct LIT_API StepResult; 
		struct LIT_API Step;
		class LIT_API App;
	};
};

namespace Lit
{
	namespace Tests
	{
		class LIT_API std::logic_error;

		enum LIT_API AppReturnCode
		{
			CODE_NONE = 0,
			CODE_SUCCESS = CODE_NONE,

			CODE_INVALID_POINTER = 1<<1,
			CODE_INVALID_STREAM = 1<<2,
			CODE_LOGIC_ERROR = 1<<3,
			CODE_RUNTIME_ERROR = 1<<4,
			CODE_FATAL_ERROR = 1<<5
		};

		struct LIT_API AppError : public std::logic_error
		{
			AppError( const String &message, AppReturnCode code = CODE_RUNTIME_ERROR );
			AppError( const AppError & err ) ;

			AppError& operator=( const AppError & err );
			
			int getCode() const;
			const String & getMessage() const;
		private:
			int Code;
			String Message;
		};
		struct LIT_API Step
		{
			Step( const String &name = "" );
			Step( const Step& rk );
			virtual ~Step( );
		
			Step& operator=( const Step& rk );
		
			Timer::Traits startTimer();
			Timer::Traits stopTimer();
			const String &	getName() const;
			const Timer&	getTimer() const;

			virtual int work() = 0;

		protected:
			String mName;
			Timer		mTimer;
		};

		struct LIT_API StepResult
		{
			StepResult();
			StepResult( const String &error, const String &name, int code, const Timer &timer );
			StepResult(const StepResult& rk);
			~StepResult();

			StepResult& operator = (const StepResult& rk);
		
			String toString() const;

			const String&		getError() const;
			const String&		getName() const;
			int						getCode() const;
			const Timer&			getTimer() const;

		protected:
			String		mError;
			String		mName;
			int				mCode;
			Timer			mTimer;
		};
		
		/*!
			std::unique_ptr <Step>
				Explicit Instantiation
		*/
		template class LIT_API	std::shared_ptr<Step>;
		
		/*!
			std::allocator <StepPtr>
				Explicit Instantiation
		*/
		template class LIT_API std::allocator<std::shared_ptr<Step>>;
		
		/*!
			std::deque <StepPtr>
				Explicit Instantiation
		*/
		template class LIT_API	std::deque<std::shared_ptr<Step>, std::allocator<std::shared_ptr<Step>>>;
		
		/*!
			std::stack <StepPtr, StepDeque>
				Explicit Instantiation
		*/
		template class LIT_API std::stack<std::shared_ptr<Step>, std::deque<std::shared_ptr<Step>, std::allocator<std::shared_ptr<Step>>>>;
		/*!
			std::unique_ptr <StepStack>
				Explicit Instantiation
		*/
		template class LIT_API std::unique_ptr<std::stack<std::shared_ptr<Step>, std::deque<std::shared_ptr<Step>, std::allocator<std::shared_ptr<Step>>>>>;
				
		typedef std::shared_ptr<Step>													StepPtr;
		typedef std::deque<StepPtr>														StepDeque;
		typedef std::stack<StepPtr, StepDeque>											StepStack;
		typedef std::unique_ptr<StepStack>												StepStackPtr;
		typedef std::unordered_map<String,StepResult>								StepResultMap;

		class LIT_API App
		{
		public:
			App( const String &app_name );
			virtual ~App();

			int start( );
			
			void addStep( Step * step );

		protected:
			virtual void processStepError( const StepResult& result );

			int _processReturns( const StepResultMap& returns );
			StepResult	_startStep( StepPtr & ptr );

		protected:
			String mName;
			StepStackPtr mSteps;
		};
		namespace Core
		{
			struct LIT_API SmartPtrStep : public Step
			{
				SmartPtrStep() : Step( "Smart Pointers" ){}
				~SmartPtrStep(){}

				int work()
				{
					Sleep( 2500 );
					throw AppError( "Invalid Pointer", CODE_INVALID_POINTER );
					return CODE_SUCCESS;
				}
			};

			template<
				const size_t str_length,
				const size_t str_count,
				const size_t iteration
			>
			struct LIT_API StringStep : public Step
			{
				static const unsigned int Size = str_length;
				static const unsigned int NumStrings = str_count;
				static const unsigned int Iteration = iteration;
				
				StringStep( )
					: Step( "String Comparator" )
				{}
				~StringStep(){}

				template<typename Str> String getTypeName() const{ 
					if( typeid(Str) == typeid(std::string) ) return "std::string";
					else if( typeid(Str) == typeid(std::wstring) ) return "std::wstring";
					else if( typeid(Str) == typeid(Lit::String) ) return "Lit::String";
					return typeid(Str).name(); 
				}

				template<typename StrType>
				struct Data
				{
					typename typedef StrType Type;

					Data() : value( Size, ' ' ), vec( NumStrings ){}
					Data( const Data& rk ):value(Size, ' '), timer(rk.timer), vec( NumStrings ){}
					~Data(){}

					Data& operator=( const Data& rk ){ time=rk.timer; return *this; }

					Timer timer;
					const Type value;
					std::vector<StrType> vec;
				};

				template<typename str_t>
				int workSub( Data<str_t> & data )
				{
					std::cout << "\tSizeof " << getTypeName<str_t>() << ": " << sizeof(str_t) << " bytes" << std::endl;
					data.timer.start();
					for( unsigned int i=0; i< Iteration; i++ )
					{
						for( unsigned j=0; j< NumStrings; j++ )
						{
							data.vec[j] = data.value;
						}
					}
					data.timer.stop();
					std::cout << "\t" << getTypeName<str_t>() << " tests done in " << data.timer.elapsedSec() << " seconds." << std::endl << std::endl;
					return 0;
				}
				int work( )
				{
					Data<std::string> data1;
					Data<std::wstring> data2;
					Data<Lit::String> data3;
					std::cout << "\tIterating " << Iteration << " times" << std::endl;
					std::cout << "\tOver " << NumStrings << " strings" << std::endl;
					std::cout << "\tOf length: " << Size << std::endl << std::endl;
					workSub<std::string>(data1);
					workSub<std::wstring>(data2);
					workSub<Lit::String>(data3);
					return CODE_SUCCESS;
				}
			};
		};

		class LIT_API CoreTestApp : public App
		{
		public:
			typedef Core::StringStep<100, 1000, 1000> StringTestType;
			CoreTestApp( ) : App( "Lit Core Tests" )
			{
				mSteps->push( StepPtr(new StringTestType()) );
				mSteps->push( StepPtr(new Core::SmartPtrStep) );
			}
			~CoreTestApp( )
			{}

			void processStepError( int code, StepPtr & ptr )
			{}
		};
	};
};

#ifndef LIT_CPP
extern "C"
{
#endif
	LIT_API std::ostream&  operator<<( std::ostream& strm, const Lit::Tests::StepResult &str );
	LIT_API std::ostream&  operator<<( std::ostream& strm, const Lit::Tests::Step &str );
	LIT_API std::ostream&  operator<<( std::ostream& strm, const Lit::Tests::AppError &str );
	LIT_API std::ostream&  operator<<( std::ostream& strm, Lit::Tests::AppReturnCode str );
#ifndef LIT_CPP
};
#endif

#endif